import { EMPTY_OBJ, ShapeFlags } from '@/shared'
import { createComponentInstance, setupComponent } from './component'
import { Fragment, Text } from './vnode'
import { createAppAPI } from './createApp'
import { effect } from '@/reactivity/effect'
import { shouldUpdateComponent } from './componentUpdateUtils'
import { queueJobs } from './scheduler'

export function createRenderer(options) {
  const {
    createElement: hostCreateElement,
    patchProp: hostPatchProp,
    insert: hostInsert,
    setElementText: hostSetElementText,
    remove: hostRemove
  } = options
  function render(vnode, container, parentComponent) {
    // patch
    patch(null, vnode, container, parentComponent, null)
  }

  // n1 旧节点 n2 新节点
  function patch(n1, n2, container, parentComponent, anchor) {
    switch (n2.type) {
      case Fragment:
        processFragment(n1, n2, container, parentComponent, anchor)
        break
      case Text:
        processText(n1, n2, container)
        break

      default:
        if (ShapeFlags.ELEMENT & n2.shapeFlag) {
          processElement(n1, n2, container, parentComponent, anchor)
        } else if (ShapeFlags.STATEFULCOMPONENT & n2.shapeFlag) {
          // 处理组件
          processComponent(n1, n2, container, parentComponent, anchor)
        }
        break
    }
  }

  // 只处理，children里面的内容
  function processFragment(n1, n2: any, container: any, parentComponent, anchor) {
    mountChildren(n2.children, container, parentComponent, anchor)
  }

  function processElement(n1, n2: any, container: any, parentComponent, anchor) {
    if (!n1) {
      mountElement(n2, container, parentComponent, anchor)
    } else {
      patchElement(n1, n2, container, parentComponent, anchor)
    }
  }
  function patchElement(n1, n2, container, parentComponent, anchor) {
    const oldProps = n1.props || EMPTY_OBJ
    const newProps = n2.props || EMPTY_OBJ

    const el = (n2.el = n1.el)
    patchChildren(n1, n2, el, parentComponent, anchor)
    patchProp(el, oldProps, newProps)
  }

  function patchChildren(n1, n2, container, parentComponent, anchor) {
    const c1 = n1.children
    const c2 = n2.children
    // 新的是文本,老的是数组
    if (n2.shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      if (n1.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 删除 children
        unmountChildren(c1)
        // 把新的 text 给 数组
        hostSetElementText(container, c2)
      }
      // 新的是文本,老的是文本
      if (n1.shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        if (c1 !== c2) {
          hostSetElementText(container, c2)
        }
      }
    } else {
      // 新的是数组，老的是文本
      if (n1.shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(container, '')
        mountChildren(c2, container, parentComponent, anchor)
      }
      // 新的是数组，老的是数组
      else {
        patchKeyedChildren(c1, c2, container, parentComponent, anchor)
      }
    }
  }

  function patchKeyedChildren(c1, c2, container, parentComponent, anchor) {
    function isSameVNodeType(n1, n2) {
      return n1.type === n2.type && n1.key === n2.key
    }
    const l2 = c2.length
    // 拿到旧的和新的，指针
    let i = 0
    let e1 = c1.length - 1
    let e2 = l2 - 1
    // 左侧的对比,确定i的值
    while (i <= e1 && i <= e2) {
      const n1 = c1[i]
      const n2 = c2[i]
      if (isSameVNodeType(n1, n2)) {
        patch(n1, n2, container, parentComponent, anchor)
      } else {
        break
      }
      i++
    }

    // 右侧的对比，确定e1,e2的值
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1]
      const n2 = c2[e2]
      if (isSameVNodeType(n1, n2)) {
        patch(n1, n2, container, parentComponent, anchor)
      } else {
        break
      }
      e1--
      e2--
    }

    // 新的比旧的多，需要挂载
    if (i > e1) {
      if (i <= e2) {
        const nextPost = e2 + 1
        const newAnchor = nextPost > l2 ? null : c2[nextPost].el
        while (i <= e2) {
          patch(null, c2[i], container, parentComponent, newAnchor)
          i++
        }
      }
    }
    // 新的比旧的少，需要删除
    else if (i > e2) {
      while (i <= e1) {
        hostRemove(c1[i].el)
        i++
      }
    }
    // 中间对比
    else {
      const s1 = i
      const s2 = i
      // 优化点,累计pacthed次数
      let patched = 0
      // 优化点，新的中间需要计算的次数
      let toBePatched = e2 - s2 + 1

      const keyToNewIndexMap = new Map()

      // 双端对比之老的与新的都有的容器,并初始为0的数组
      const newIndexToOldIndexMap = new Array(toBePatched)
      // 优化，当需要使用getSequence才使用
      let moved = false
      let maxNewIndexSoFar = 0

      for (let i = 0; i < toBePatched; i++) {
        newIndexToOldIndexMap[i] = 0
      }
      // 先对新的中间的元素进行映射
      for (let index = s2; index <= e2; index++) {
        keyToNewIndexMap.set(c2[index].key, index)
      }
      let newIndex
      for (let index = s1; index <= e1; index++) {
        const preChild = c1[index]
        // 如果新的都patch过了，老的还有剩余的，那么老的还有剩余的就删除
        if (patched >= toBePatched) {
          hostRemove(preChild.el)
          continue
        }
        // 如果有key，就使用key
        if (preChild.key !== null) {
          newIndex = keyToNewIndexMap.get(preChild.key)
        }
        // 如果没有key
        else {
          for (let j = s2; j <= e2; j++) {
            if (isSameVNodeType(preChild, c2[j])) {
              newIndex = j
              break
            }
          }
        }
        // 说明在新的里面没找到，删除旧的
        if (newIndex === undefined) {
          hostRemove(preChild.el)
        }
        // 否则，就patch,进行深度比对
        else {
          if (newIndex >= maxNewIndexSoFar) {
            maxNewIndexSoFar = newIndex
          } else {
            moved = true
          }
          newIndexToOldIndexMap[newIndex - s2] = i + 1
          patch(preChild, c2[newIndex], container, parentComponent, null)
          patched++
        }
      }
      const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []

      let j = increasingNewIndexSequence.length - 1
      for (let i = toBePatched; i >= 0; i--) {
        const nextIndex = i + s2
        const nextChild = c2[nextIndex]
        const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null
        if (newIndexToOldIndexMap[i] === 0) {
          patch(null, nextChild, container, parentComponent, anchor)
        } else if (moved) {
          if (j < 0 || i !== increasingNewIndexSequence[j]) {
            console.log('移动')
            hostInsert(nextChild.el, container, anchor)
          } else {
            j--
          }
        }
      }
    }
  }
  function unmountChildren(children) {
    for (let i = 0; i < children.length; i++) {
      const el = children[i].el
      hostRemove(el)
    }
  }
  function patchProp(el, oldProps, newProps) {
    if (oldProps !== newProps) {
      for (const key in newProps) {
        const prevProp = oldProps[key]
        const nextProp = newProps[key]
        if (prevProp !== nextProp) {
          hostPatchProp(el, key, prevProp, nextProp)
        }
      }
      if (oldProps !== EMPTY_OBJ) {
        for (const key in oldProps) {
          if (!(key in newProps)) {
            hostPatchProp(el, key, oldProps[key], null)
          }
        }
      }
    }
  }

  function mountElement(n2: any, container: any, parentComponent: any, anchor) {
    const { type, props, children, shapeFlag } = n2
    const el = (n2.el = hostCreateElement(type))
    if (ShapeFlags.TEXT_CHILDREN & shapeFlag) {
      el.textContent = children
    } else if (ShapeFlags.ARRAY_CHILDREN & shapeFlag) {
      mountChildren(children, el, parentComponent, anchor)
    }
    for (const key in props) {
      const value = props[key]

      // if (isOn(key)) {
      //   el.addEventListener(key.slice(2).toLowerCase(), value)
      // }
      // el.setAttribute(key, value)
      hostPatchProp(el, key, null, value, anchor)
    }
    // container.append(el)
    hostInsert(el, container, anchor)
  }

  function mountChildren(children, container, parentComponent, anchor) {
    children.forEach((v) => {
      patch(null, v, container, parentComponent, anchor)
    })
  }
  function processComponent(n1, n2: any, container: any, parentComponent, anchor) {
    if (!n1) {
      mountComponent(n2, container, parentComponent, anchor)
    } else {
      updateComponent(n1, n2, container)
    }
  }

  function updateComponent(n1, n2, container) {
    console.log('更新组件', n1, n2)
    const instance = (n2.component = n1.component)
    if (shouldUpdateComponent(n1, n2)) {
      instance.next = n2
      instance.update()
    } else {
      n2.el = n1.el
      instance.vnode = n2
    }
  }
  function mountComponent(initialVNode: any, container: any, parentComponent, anchor) {
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent
    ))
    setupComponent(instance)
    setupRenderEffect(instance, initialVNode, container, anchor)
  }
  function setupRenderEffect(instance: any, initialVNode: any, container: any, anchor) {
    //  subTree就是h()
    instance.update = effect(
      () => {
        // 初始挂载逻辑
        if (!instance.isMounted) {
          const { proxy } = instance

          const subTree = (instance.subTree = instance.render.call(proxy))
          patch(null, subTree, container, instance, anchor)

          initialVNode.el = subTree.el
          instance.isMounted = true
        } else {
          const { next, vnode } = instance
          if (next) {
            next.el = vnode.el

            updateComponentPreRender(instance, next)
          }
          // 更新逻辑
          const { proxy } = instance

          const subTree = instance.render.call(proxy)
          const prevSubTree = instance.subTree
          patch(prevSubTree, subTree, container, instance, anchor)
        }
      },
      {
        scheduler() {
          queueJobs(instance.update)
        }
      }
    )
  }

  function updateComponentPreRender(instance, nextVNode) {
    instance.vnode = nextVNode
    instance.next = null
    instance.props = nextVNode.props
  }
  function processText(n1, n2: any, container: any) {
    const text = (n2.el = document.createTextNode(n2.children))
    container.append(text)
  }
  return {
    createApp: createAppAPI(render)
  }
}

function getSequence(arr) {
  const p = arr.slice()
  const result = [0]
  let i, j, u, v, c
  const len = arr.length
  for (i = 0; i < len; i++) {
    const arrI = arr[i]
    if (arrI !== 0) {
      j = result[result.length - 1]
      if (arr[j] < arrI) {
        p[i] = j
        result.push(i)
        continue
      }
      u = 0
      v = result.length - 1
      while (u < v) {
        c = (u + v) >> 1
        if (arr[result[c]] < arrI) {
          u = c + 1
        } else {
          v = c
        }
      }
      if (arrI < arr[result[u]]) {
        if (u > 0) {
          p[i] = result[u - 1]
        }
        result[u] = i
      }
    }
  }
  u = result.length
  v = result[u - 1]
  while (u-- > 0) {
    result[u] = v
    v = p[v]
  }
  return result
}

// console.log(getSequence([4, 2, 3, 1, 5]))
